package org.toves.checklua.ast;

import java.util.List;

import org.toves.checklua.scan.Token;


public class AstNodes {
	public static AstNode forBinop(Token operator, AstNode left, AstNode right) {
		// { BINOP, operatorToken, [ AstNode, AstNode ] }
		return new AstNode(AstNode.BINOP, operator, new Object[] { left, right });
	}
	
	public static AstNode forUnop(Token operator, AstNode operand) {
		// { UNOP, operatorToken, [ AstNode ] }
		return new AstNode(AstNode.UNOP, operator, new Object[] { operand });
	}
	
	public static AstNode forConst(Token constToken, Object value) {
		// { CONST, constToken, [ Object ] }
		return new AstNode(AstNode.CONST, constToken, new Object[] { value });
	}
	
	public static AstNode forName(Token nameToken) {
		// { NAME, nameToken, [ ] }
		return new AstNode(AstNode.NAME, nameToken, null);
	}
	
	public static AstNode forCall(Token parenToken, AstNode func, List<AstNode> args) {
		// { CALL, parenToken, [ AstNode (function), AstNode (arg0), AstNode (arg1), ... ] }
		Object[] subs = new Object[args == null ? 1 : 1 + args.size()];
		subs[0] = func;
		if (args != null) {
			for (int i = 0; i < args.size(); i++) subs[i + 1] = args.get(i);
		}
		return new AstNode(AstNode.CALL, parenToken, subs); 
	}
	
	public static AstNode forIndex(Token brackToken, AstNode lhs, AstNode inside) {
		// { INDEX, brackToken, [ AstNode (lhs), AstNode (index) ] }
		return new AstNode(AstNode.INDEX, brackToken, new Object[] { lhs, inside });
	}
	
	public static AstNode forField(Token dotToken, AstNode lhs, String fieldName) {
		// { FIELD, dotToken, [ AstNode (lhs), String (fieldName) ] } 
		return new AstNode(AstNode.FIELD, dotToken, new Object[] { lhs, fieldName });
	}
	
	public static AstNode forMethod(Token colonToken, AstNode lhs, String fieldName) {
		// { METHOD, colonToken, [ AstNode (lhs), String (fieldName) ] }
		return new AstNode(AstNode.METHOD, colonToken, new Object[] { lhs, fieldName });
	}
	
	public static AstNode forTableConstructor(Token braceToken, List<AstNode> fields) {
		// { COSNTRUCTOR, braceToken, [ AstNode (key0), AstNode (val0), AstNode (key1), AstNode (val1), ... ] }
		Object[] subs = fields.toArray(new Object[fields.size()]);
		return new AstNode(AstNode.CONSTRUCTOR, braceToken, subs);
	}
	
	//
	// statement nodes
	//
	public static AstNode forEmpty() {
		// { EMPTY, null, null }
		return new AstNode(AstNode.EMPTY, null, null);
	}
	
	public static AstNode forSeq(Token reprToken, List<AstNode> stats) {
		// { SEQ, reprToken, [ AstNode (stmt0), AstNode (stmt1), ... ] }
		Object[] o = stats.toArray(new Object[stats.size()]);
		return new AstNode(AstNode.SEQ, reprToken, o);
	}
	
	public static AstNode forAssn(Token assnToken,
			List<AstNode> lvalues, List<AstNode> rvalues) {
		// { ASSN, assnToken, [ [ AstNode (lval0), AstNode (lval1), ... ], [ AstNode (rval0), AstNode (rval1), ... ] ] }
		// -or-
		// { ASSN, assnToken, [ AstNode (lval), AstNode (rval) ] }
		for (AstNode lval : lvalues) {
			lval.setDefineGlobal();
		}
		if (lvalues.size() > 1 || rvalues.size() > 1) {
			AstNode[] a = lvalues.toArray(new AstNode[lvalues.size()]);
			AstNode[] b = rvalues.toArray(new AstNode[rvalues.size()]);
			AstNode ret = new AstNode(AstNode.ASSN, assnToken, new Object[] { a, b });
			return ret;
		} else {
			Object[] args = { lvalues.get(0), rvalues.get(0) };
			return new AstNode(AstNode.ASSN, assnToken, args);
		}
	}
	
	public static AstNode forAssnLocal(Token assnToken, List<Token> lvalues,
			List<AstNode> rvalues) {
		// { ASSN, assnToken, [ [ AstNode (lval0), AstNode (lval1), ... ], [ AstNode (rval0), AstNode (rval1), ... ] ] }
		// -or-
		// { ASSN, assnToken, [ AstNode (lval), AstNode (rval) ] }
		AstNode[] names = createNameNodes(lvalues, true);
		if (names.length > 1 || (rvalues != null && rvalues.size() > 1)) {
			AstNode[] b = rvalues == null ? null : rvalues.toArray(new AstNode[rvalues.size()]);
			return new AstNode(AstNode.ASSN_LOCAL, assnToken, new Object[] { names, b });
		} else {
			Object[] args = { names[0], rvalues == null ? null : rvalues.get(0) };
			return new AstNode(AstNode.ASSN_LOCAL, assnToken, args);
		}
	}
	
	public static AstNode forDo(Token doToken, AstNode body) {
		// { DO, doToken, [ AstNode (body) ] }
		return new AstNode(AstNode.DO, doToken, new Object[] { body });
	}
	
	public static AstNode forIf(Token ifToken, AstNode exp, AstNode bodyThen,
			AstNode bodyElse) {
		// { IF, ifToken, [ AstNode (exp), AstNode (bodyThen), AstNode (bodyElse) ] }
		return new AstNode(AstNode.IF, ifToken, new Object[] { exp, bodyThen, bodyElse });
	}
	
	public static AstNode forWhile(Token whileToken, AstNode cond, AstNode body) {
		// { WHILE, whileToken, [ AstNode (cond), AstNode (body) ] }
		return new AstNode(AstNode.WHILE, whileToken, new Object[] { cond, body });
	}
	
	public static AstNode forRepeat(Token repeatToken, AstNode body, AstNode cond) {
		return new AstNode(AstNode.REPEAT, repeatToken, new Object[] { body, cond });
	}
	
	public static AstNode forForInts(Token forToken, Token nameToken,
			AstNode start, AstNode stop, AstNode step, AstNode body) {
		AstNode lval = forName(nameToken);
		lval.setDefineLocal();
		return new AstNode(AstNode.FOR_INTS, forToken,
				new Object[] { lval, start, stop, step, body });
	}
	
	public static AstNode forForEach(Token forToken, List<Token> nameTokens,
			List<AstNode> exprNodes, AstNode body) {
		AstNode[] names = createNameNodes(nameTokens, true);
		AstNode[] et = exprNodes.toArray(new AstNode[exprNodes.size()]);
		return new AstNode(AstNode.FOR_INTS, forToken, new Object[] { names, et, body });
	}
	
	public static AstNode forReturn(Token returnToken, List<AstNode> exprs) {
		Object[] parms;
		if (exprs.size() == 0) {
			parms = null;
		} else {
			parms = exprs.toArray(new AstNode[exprs.size()]);
		}
		return new AstNode(AstNode.RETURN, returnToken, parms);
	}
	
	public static AstNode forBreak(Token breakToken) {
		return new AstNode(AstNode.BREAK, breakToken, null);
	}
	
	public static AstNode forFunctionDef(Token functionToken, AstNode name,
			List<Token> parmTokens, AstNode body) {
		if (name != null) name.setDefineGlobal();
		AstNode[] parms = createNameNodes(parmTokens, true);
		return new AstNode(AstNode.DEF_FUNC, functionToken, new Object[] { name, parms, body });
	}
	
	public static AstNode forFunctionDefLocal(Token functionToken, AstNode name,
			List<Token> parmTokens, AstNode body) {
		if (name != null) name.setDefineLocal();
		AstNode[] parms = createNameNodes(parmTokens, true);
		return new AstNode(AstNode.DEF_FUNC_LOCAL, functionToken, new Object[] { name, parms, body });
	}
	
	private static AstNode[] createNameNodes(List<Token> nameTokens, boolean local) {
		AstNode[] nodes = new AstNode[nameTokens.size()];
		int nodesPos = 0;
		for (Token token : nameTokens) {
			AstNode node = forName(token);
			if (local) node.setDefineLocal();
			nodes[nodesPos] = node;
			nodesPos++;
		}
		return nodes;
	}
}
